So far you have seen that you can take a workflow
between Visio and SharePoint Designer multiple times, and allow the
business user to express their workflow desires to a great length.
However, there will be situations where you will need to involve a
developer. The situations are twofold, either you need to use an
activity that you cannot find in SharePoint Designer, in which case you
will have to author a custom activity in Visual Studio 2010 and use it
in your workflows. Alternatively, your workflow involves crazy
proprietary calculations or integration with external systems, stuff
that you cannot expect SharePoint to provide out of the box.
In either of these scenarios, you have the ability to
use Visual Studio. Visual studio has the ability to import a .wsp
package, which was in turn exported from SharePoint Designer, and could
contain workflow definitions. Or you can use Visual Studio to craft up a
brand new workflow from scratch!
In order to write a brand new workflow in Visual
Studio, start Visual Studio and create a new project called
"RollOfDiceWF" based on the empty SharePoint project template. Since
this project will contain workflow templates, you need to make this a
farm solution. What I intend to do in this workflow, is begin by rolling
dice. Rolling dice should give me a random number between one to six
and that randomly generated number will be updated in the title of the
list item the workflow is running upon.
Thus, right-click the project and choose to add a new
SPI of type "Sequential Workflow". You could also choose to add a state
machine workflow. Any process can be represented as either a sequential
workflow, or a state machine workflow. However, it is generally easier
to represent machine involving tasks that you can easily think of as
flowcharts as sequential workflows. It is generally easier to represent
workflows that go through various states have long pauses between them
and involve human being or external system interaction as state machine
workflows.
As soon as you choose to add a new sequential workflow, Visual Studio will ask you a couple of questions.
It will ask if this is supposed to be a site workflow or a list workflow. In this case, choose to make it a list workflow.
The
second and optional step here is to automatically associate this
workflow to a list and also specify a tasks and workflow history list.
This is a convenience that Visual Studio provides you which facilitate
easy debugging; you could assign a workflow yourself manually if you
wished. However, associate the workflow with a list called "Test" based
on the custom list template. Also, if your site collection currently
doesn't have a tasks and workflow history list, just create an
association to any out of the box workflow through the browser, and that
will give you an option to create the tasks and workflow history list.
As you will note, once the sequential workflow has
been added the first activity in the workflow is the onWorkflowActivated
activity. This can be seen in the Figure 1.
The first activity in any SharePoint workflow has to
be the onWorkflowActivated activity. The responsibility of this activity
is to set the various context variables, such as the list item you are
operating upon, the user that started the workflow, etc. Therefore, when
you're adding more activities into your workflow you must always ensure
that your activities fall below the onWorkflowActivated activity.
Also, note that in the toolbox you have the ability
to add activities from workflow foundation 3.0, workflow foundation 3.5,
and SharePoint workflow activities. .NET activities are also usable
within SharePoint workflows, but you must be careful of not using
certain activities in SharePoint workflows such as TransactionScope
activity, CompensatableTransactionScope activity, SynchronizationScope
activity etc.
At this point, drag and drop a code activity and
place it below the onWorkflowActivated1 activity in your sequential
workflow. As soon as you drag and drop the activity, you would see a red
exclamation mark on the code activity, which is informing you that
further work needs to be done before this workflow is complete.
Double-click the code activity to create its MethodInvoking event
handler, and add the code shown in Listing 1 into this event handler.
Example 1. MethodInvoking event handler for codeActivity
private int diceRoll = 0;
private void codeActivity1_ExecuteCode(object sender, EventArgs e)
{
Random rnd = new Random();
diceRoll = rnd.Next(1, 6);
workflowProperties.Item["Title"] = diceRoll;
workflowProperties.Item.Update();
}
|
As you can see from Listing 10-1,
you're generating a random number between one and six and updating the
items title with the generated number. Next, build and deploy your
workflow, and then run the workflow on a new list item that you create
in the test list you created earlier. Note that the title of the list
changes at random between one to six.
Since you're rolling dice, let's make this a little
bit more interesting. The idea here is that anytime you get a number
greater than two, a task should be created for you allowing you to win a
prize. In order to do so, drop an ifElseActivity under the code
activity. This ifElseActivity will have two ifElse branches. You need
only one, so go ahead and delete one of those branches. Inside the
ifElse branch that is left, drag and drop the createTask activity from
under the SharePoint workflow, workflow activities category. Your newly
added section in the sequential workflow should look like Figure 2.
The red exclamation marks signify that there is some
additional work that needs to be done on those activities to properly
configure them. Specifically, the if else branch activity needs a
condition supplied on it. Select the if else branch activity and in its
properties choose to specify a declarative condition. Give the condition
name as "isWinner", and the Expression as "this.diceRoll > 2".
By specifying the condition in this manner, you will
cause the create task activity to be called whenever the diceRoll value
is greater than two. The next thing you need to do is to configure the
create task activity.
The createTask activity requires a correlation token.
Correlation tokens are an integral concept to workflows. Workflows run
in a workflow host, and a number of instances of the workflow are
multiplexed in a single running instance of an in-memory workflow class.
Between various activity executions the workflow can be paused and
persisted back to the persistence database, in this case the content
database, and then rehydrated as necessary in the future. For the
workflow host to keep everything straight between multiple workflow
instances but a single in-memory instance of the workflow, workflow
foundation relies on correlation tokens. Note that you already have a
correlation token for the entire workflow. Since there can be many tasks
within a single workflow, you need to create a new correlation token
for the task. Therefore, edit the properties of the create task
activity, and under the correlation token, type the new correlation
token and call it "taskToken". You will also have to specify an owner
activity name. At this point, you can really pick any other activity
name that is at the parent level of the createTask activity, but choose
to make the workflow itself as the owner activity name.
The next thing you need to do is to specify values
for the TaskID and TaskProperties of the create task activity. To do so,
click the ellipse by each one of these, and go to the "bind to a new
member" tab then choose to create a field, as shown in Figure 3.
Repeat this procedure for TaskProperties. Once you
have configured the create task activity, the properties pain should
look like Figure 4.
Next, double-click the create task activity to create
a MethodInvoking event handler for it. The MethodInvoking event handler
is a great opportunity for you to set properties on the task before the
task actually gets created. In this event handler, put the code as
shown in Listing 2.
Example 2. Code for the createTask MethodInvoking Event Handler
private void createTask1_MethodInvoking(object sender, EventArgs e)
{
createTask1_TaskId1 = Guid.NewGuid();
createTask1_TaskProperties1.AssignedTo = workflowProperties.Originator;
createTask1_TaskProperties1.Title = "Congratulations!!";
createTask1_TaskProperties1.Description =
"You have won!!! Now go and claim your prize";
createTask1_TaskProperties1.SendEmailNotification = true;
}
|
As you can see in the MethodInvoking event handler
for the create task activity, you're specifying the task title to task
descriptions and sending an e-mail and and assigning the task to the
user that originated the workflow.
Rebuild and redeploy this workflow and execute it on a
list item again. Run the workflow a couple of times, until you get a
value greater than two. Note that whenever you get a value greater than
2, a task is created for you in the tasks list.
Now let's make this workflow even more interesting.
What if at the beginning of each workflow you could pick, "On whose
behalf I'm playing this game". In other words, when the workflow is
started you could present an initiation form with a dropdown
prepopulated with the list of users in the site.
In order to add an initiation form in the workflow,
first add the layouts mapped folder in your project. Once the layouts
folder has been added, right-click your project and choose to add the
new SPI. When prompted to pick the kind of SPI you're adding choose to
add a workflow initiation form. Add this workflow initiation form at
_layouts\RollOfDiceWF\WFInitiationPlayer.aspx.
Your workflow initiation form has been added to the
project. It will now be deployed with the project. But you need to do
three things to actually make it work with your workflow.
You need to edit the initiation form so it presents the user with a dropdown with the list of users in the site.
You
need to tell the workflow that the workflow needs to present an
initiation form to the end user whenever the workflow is instantiated.
Finally,
in the MethodInvoking event handler off your createTask activity,
instead of assigning the POS to the workflow originator you need to
assign the task to whoever the user picked in the initiation form.
Let's go implement the previous three steps one by
one. Step one is to edit the initiation form so it presents the user
with the dropdown list of users in the site. In order to do so in the
placeholdermain ContentPlaceHolder of your initiation form, add the code
shown in Listing 3.
Example 3. Code Necessary to Present the User with the Dropdown with a List of Users
<asp:Content ID="Main" ContentPlaceHolderID="PlaceHolderMain" runat="server">
<SharePoint:SPDataSource runat="server" ID="usersList" DataSourceMode="List"
SelectCommand="<Query><OrderBy><FieldRef Name='Title'
Ascending='true'/></OrderBy></Query>">
<SelectParameters>
<asp:Parameter Name="ListName" DefaultValue="User Information List" />
</SelectParameters>
</SharePoint:SPDataSource>
On whose behalf are you playing?
<br />
<asp:DropDownList ID="userName" runat="server" DataSourceID="usersList"
DataTextField="Title" DataValueField="Account">
</asp:DropDownList>
<asp:Button ID="StartWorkflow" runat="server" OnClick="StartWorkflow_Click" Text="Start
Workflow" />
<asp:Button ID="Cancel" runat="server" OnClick="Cancel_Click" Text="Cancel" />
</asp:Content>
|
As you can see from Listing 3,
you're creating an SPDatasource object bound to the "User Information
List". The "User Information List" is a hidden list present in any site
collection that gives me a list of users in the site collection. You're
then data binding that data source to a simple DropDownList.
The second thing you need to do is to tell your
workflow that the initiation form needs to be popped up whenever the
workflow is instantiated. To do so, you need to edit the workflow
element in the element.xml that defines your workflow. The workflow
element in the elements.xml is shown in Listing 4. Note that my workflow name is DiceRoll, and you need to appropriately reflect the CodeBesideClass attribute in your code.
Example 4. The Edited Workflow Element
<Workflow
Name="RollOfDiceWF - DiceRoll"
Description="My SharePoint Workflow"
Id="05b3d065-ad8a-4a9b-b808-aa32eab22057"
InstantiationUrl="/_layouts/RollOfDiceWF/WFInitiationPlayer.aspx"
CodeBesideClass="RollOfDiceWF.DiceRoll.DiceRoll"
CodeBesideAssembly="$assemblyname$">
|
As you can see from Listing 10-4,
you have added a new attribute called InstantiationUrl. At this point,
play with the intellisense offered by the xml schema for elements.xml
and try to discover how you will specify an association form a
modification form and a task form. Note that all of these details are
specified in a manner similar to InstantiationURL.
You've done the first two steps, which will pop open
an instantiation form and show the appropriate drop down. The last thing
you need to do is to assign the picked value from the dropdown in the
create task MethodInvoking method handler. This last step actually
involves two steps. The first step is to populate the initiation data.
This is done in the code behind of the initiation form in a method
called GetInitiationData. The code for this method is as follows:
private string GetInitiationData()
{
return userName.Text;
}
You're simply returning the picked username text. If
you add multiple initiation variables perhaps it is a good idea to
return an xml formatted string.
Next, in MethodInvoking event handler of the create task activity comment out the following line:
// createTask1_TaskProperties1.AssignedTo = workflowProperties.Originator;
Instead, replace it with the following code line:
createTask1_TaskProperties1.AssignedTo = workflowProperties.InitiationData;
As you can see, instead of assigning the task to the
user that originated the workflow, you are assigning the workflow to the
user that was picked in the drop down list.
Your workflow changes are now complete so go ahead
and rebuild and redeploy the workflow. Run a workflow instance on a list
item, and you will note that an initiation form, as shown in Figure 5.
Pick a user other than the logged in user and click
the start workflow button. Keep playing this game until you get a value
greater than two. Once you get a value greater than two, visit the tasks
list and look at the task created by this workflow. Note that the task
has been created for the user that you picked in the dropdown in the
initiation form. This can be seen in Figure 6.
This is how you can write workflows in Visual Studio
2010 and add forms into those workflows allowing them to interact with
the user.